#include "categorizermodel.h"
#include "other/modelcommon.h"

#include <QAbstractItemModel>
#include <QDebug>

const int CATEGORY_ID = -20; // Magic number

// \TODO: Handle rows moved.

CategorizerModel::CategorizerModel(const QAbstractItemModel *categoryModel,
                                   const QAbstractItemModel *categorizableModel,
                                   QObject *parent) :
    QAbstractItemModel(parent)
{
    Q_ASSERT(categoryModel && categorizableModel);
    _categoryModel = categoryModel;
    _itemModel = categorizableModel;

    // Set up categoryModel signals

    connect(categoryModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
            this, SLOT(category_dataChanged(QModelIndex,QModelIndex)));
    connect(categoryModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
            this, SLOT(category_rowsAboutToBeInserted(QModelIndex,int,int)));
    connect(categoryModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
            this, SLOT(category_rowsAboutToBeRemoved(QModelIndex,int,int)));
    /*connect(categoryModel, SIGNAL(rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)),
            this, SLOT(category_rowsAboutToBeMoved(QModelIndex,int,int,QModelIndex,int)));*/
    connect(categoryModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
            this, SLOT(category_rowsInserted(QModelIndex,int,int)));
    /*connect(categoryModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
            this, SLOT(category_rowsMoved()));*/
    connect(categoryModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
            this, SLOT(category_rowsRemoved()));
    connect(categoryModel, SIGNAL(modelAboutToBeReset()), this, SLOT(common_modelAboutToBeReset()));
    connect(categoryModel, SIGNAL(modelReset()), this, SLOT(common_modelReset()));

    // Set up categorizableModel (itemModel) signals

    connect(_itemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this,
            SLOT(item_dataChanged(QModelIndex,QModelIndex)));
    connect(_itemModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
            this, SLOT(item_rowsAboutToBeRemoved(QModelIndex,int,int)));
    connect(_itemModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
            this, SLOT(item_rowsInserted(QModelIndex,int,int)));

    connect(_itemModel, SIGNAL(modelAboutToBeReset()), this, SLOT(common_modelAboutToBeReset()));
    connect(_itemModel, SIGNAL(modelReset()), this, SLOT(common_modelReset()));

    // Add "No category" element
    itemList.append(QList<QPersistentModelIndex>());
}

bool CategorizerModel::isCategory(const QModelIndex& index) const {
    return index.isValid() && !index.parent().isValid();
}

QVariant CategorizerModel::data(const QModelIndex &index, int role) const {
    if(!index.isValid())
        return QVariant();
    if(isCategory(index)) {
        if(index.row() == 0 && role == Qt::DisplayRole)
            return tr("No category");
        return _categoryModel->data(_categoryModel->index(index.row()-1, index.column()), role);
    } else {
        // It is an item

        // This row in itemModel is stored in itemList
        QModelIndex rowInItemModel = itemList.at(index.parent().row()).at(index.row());
        return rowInItemModel.data(role);
    }
}

int CategorizerModel::rowCount (const QModelIndex &parent) const {
    if(!parent.isValid())
        return _categoryModel->rowCount() + 1;
    else if(isCategory(parent))
        // \NOTE: This could cause problems if categoryModel has children
        return itemList.at(parent.row()).size();
    return 0;
}

Qt::ItemFlags CategorizerModel::flags(const QModelIndex &index) const {
    if (!index.isValid())
        return 0;

    return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

QModelIndex CategorizerModel::index(int row, int column,
                  const QModelIndex &parent) const {
    // \NOTE: This could cause problems if categoryModel has children

    // Top level items are categories, children are items.
    int id = parent.isValid() ? parent.row() : CATEGORY_ID;
    return hasIndex(row,column,parent) ? createIndex(row, column, id) : QModelIndex();
}

QModelIndex CategorizerModel::parent(const QModelIndex &index) const {
    // If it is invalid or is a category, it doesn't have parent
    if(!index.isValid() || index.internalId() == CATEGORY_ID)
        return QModelIndex();

    return this->index(index.internalId(), 0);
}

int CategorizerModel::columnCount(const QModelIndex &parent) const {
    // Default implementation return the columnCount of the CategorizableModel

    Q_UNUSED(parent);
    return _itemModel->columnCount(QModelIndex());
}

void CategorizerModel::addItem(int row) {
    // Find the right category parent
    QModelIndex itemIndex = _itemModel->index(row,0);
    QModelIndex categoryParent = getCategoryParentForIndex(itemIndex);

    // Add to the end
    int pos = rowCount(categoryParent);
    beginInsertRows(categoryParent, pos, pos);
    // Save the original index in itemList
    itemList[categoryParent.row()].append(QPersistentModelIndex(itemIndex));
    endInsertRows();
}

void CategorizerModel::updateItem(int row) {
    QModelIndex currentIndex = findRow(row);
    if(!currentIndex.isValid()) {
        addItem(row);
        return;
    }
    QModelIndex oldParent = currentIndex.parent();
    QModelIndex newParent = getCategoryParentForIndex(currentIndex);

    if(oldParent == newParent)
        return;
    int oldPos = currentIndex.row();
    int newPos = rowCount(newParent);
    beginMoveRows(oldParent, oldPos, oldPos, newParent, newPos);
    itemList[newParent.row()].append(itemList[oldParent.row()].takeAt(oldPos));

    endMoveRows();
}

void CategorizerModel::removeItem(int row) {
    QModelIndex currentIndex = findRow(row);
    beginRemoveRows(currentIndex.parent(), currentIndex.row(), currentIndex.row());
    itemList[currentIndex.parent().row()].removeAt(currentIndex.row());
    endRemoveRows();
}

void CategorizerModel::addCategory(int row, bool moveRows) {
    beginInsertRows(QModelIndex(), row, row);
    itemList.insert(row,QList<QPersistentModelIndex>());
    endInsertRows();

    if(moveRows)
        checkAndMoveUncategorizedItems();
}

void CategorizerModel::updateCategory(int row) {
    ++row;
    QModelIndex category = index(row,0);

    if(category.data(ModelCommon::KeyRole) != getCategoryForIndex(index(0,0,category))) {
        // Category key (probably) changed. Check if items in "No category" match the new key and move
        // current children to "No category"

        // Move items that match the new category
        checkAndMoveUncategorizedItems();

        int oldCount = itemList.at(row).size();
        if(oldCount > 0) {
            // Move old items to "No category"
            QModelIndex noCategory = index(0,0);
            beginMoveRows(category, 0, oldCount-1, noCategory, itemList.at(0).size());
            for(int i = 0; i<oldCount; ++i) {
                itemList[0].append(itemList[row].takeAt(0));
            }
            endMoveRows();
        }
    }
}

void CategorizerModel::removeCategory(int row) {
    ++row;

    QModelIndex category = index(row,0);
    int oldCount = itemList.at(row).size();

    if(oldCount > 0) {
        // Move old items to "No category"
        QModelIndex noCategory = index(0,0);
        beginMoveRows(category, 0, oldCount-1, noCategory, itemList.at(0).size());
        itemList[0].append(itemList.at(row));
        itemList.removeAt(row);
        endMoveRows();
    }
}

void CategorizerModel::checkAndMoveUncategorizedItems() {
    QModelIndex noCategory = index(0,0);
    for(int i = itemList.at(0).size()-1; i>=0; --i) {
        QModelIndex row = itemList.at(0).at(i);
        QModelIndex newParent = getCategoryParentForIndex(row);
        if(newParent != noCategory) {
            beginMoveRows(noCategory,i,i, newParent, rowCount(newParent));
            itemList[newParent.row()].append(itemList[0].takeAt(i));
            endMoveRows();
        }
    }
}

// Private

QString CategorizerModel::getCategoryForIndex(const QModelIndex& itemIndex) const {
    return itemIndex.data(ModelCommon::CategoryRole).toString();
}

QModelIndex CategorizerModel::getCategoryParentForIndex(const QModelIndex& itemIndex) const {
    Q_ASSERT(itemIndex.isValid());

    // Look for item with the category key in ModelCommon::KeyRole

    QModelIndex parent = _categoryModel->match(_categoryModel->index(0, 0),
                                               ModelCommon::KeyRole, itemIndex.data(ModelCommon::CategoryRole), 1,
                                               Qt::MatchFixedString | Qt::MatchCaseSensitive).value(0);
    return parent.isValid() ? index(parent.row()+1,0) : index(0,0);
}

QModelIndex CategorizerModel::findRow(int row) const {
    for(int i = 0; i<itemList.size(); ++i) {
        for(int j = 0; j<itemList.at(i).size(); ++j) {
            if(itemList.at(i).at(j).row() == row)
                return index(j, 0, index(i,0));
        }

    }
    return QModelIndex();
}

// Private slots


void CategorizerModel::category_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) {
    for(int i = topLeft.row(); i<=bottomRight.row(); ++i)
        updateCategory(i);
}

void CategorizerModel::category_rowsAboutToBeInserted(const QModelIndex &parent, int first, int last) {
    Q_ASSERT(parent.isValid() == false);
    // \TODO: Remove this
}

/*void CategorizerModel::category_rowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart,
                          int sourceEnd, const QModelIndex &destinationParent,
                          int destinationRow) {
    Q_UNUSED(sourceParent);
    Q_UNUSED(destinationParent);
    ++sourceStart;
    ++sourceEnd;
    ++destinationRow;
    beginMoveRows(QModelIndex(), sourceStart, sourceEnd, QModelIndex(), destinationRow);
    for(int i = sourceEnd; i >= sourceStart; --i) {

    }
}

void CategorizerModel::category_rowsMoved() {

}*/

void CategorizerModel::category_rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last) {
    Q_ASSERT(parent.isValid() == false);
    for(int i = last; i>=first; --i)
        removeCategory(i);
}

void CategorizerModel::category_rowsInserted(const QModelIndex &parent, int first, int last) {
    Q_ASSERT(parent.isValid() == false);

    for(int i = first; i<=last; ++i)
        addCategory(i, false);
    checkAndMoveUncategorizedItems();
}

void CategorizerModel::category_rowsRemoved() {
    endRemoveRows();
}

// Slots for categorizable model

void CategorizerModel::item_dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) {
    for(int i = topLeft.row(); i<=bottomRight.row(); ++i)
        updateItem(i);
}

void CategorizerModel::item_rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last) {
    Q_ASSERT(parent.isValid() == false);
    for(int i = last; i>=first; --i)
        removeItem(i);
}

void CategorizerModel::item_rowsInserted(const QModelIndex &parent, int first, int last) {
    Q_ASSERT(parent.isValid() == false);
    for(int i = first; i<=last; ++i)
        addItem(i);
}

void CategorizerModel::common_modelAboutToBeReset() {
    beginResetModel();
}

void CategorizerModel::common_modelReset() {
    // Remove all categories
    itemList.clear();
    // Add "No category"
    itemList.append(QList<QPersistentModelIndex>());

    // Add categories in categoryModel
    int categoryCount = _categoryModel->rowCount();
    for(int i = 0; i<categoryCount; ++i)
        addCategory(i);

    int itemCount = _itemModel->rowCount();
    for(int i = 0; i<itemCount; ++i)
        addItem(i);
    endResetModel();
}
